package fr.cephb.joperon.tool.snpinfo;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import java.util.regex.Pattern;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import java.util.zip.ZipOutputStream;

import com.sleepycat.db.DatabaseException;

import fr.cephb.io.InputSource;
import fr.cephb.io.TmpWriter;
import fr.cephb.joperon.core.Fid;
import fr.cephb.joperon.core.Operon;
import fr.cephb.joperon.core.Ymd;
import fr.cephb.joperon.core.bio.AcidNucleicUtil;
import fr.cephb.joperon.core.bio.Assembly;
import fr.cephb.joperon.core.db.GtypesDB;
import fr.cephb.joperon.core.entities.Gtypes;
import fr.cephb.joperon.core.entities.MPanel;
import fr.cephb.joperon.core.entities.PrjDna;
import fr.cephb.joperon.core.entities.PrjSeq;
import fr.cephb.joperon.core.entities.SnpIds;
import fr.cephb.joperon.core.entities.SnpMap;
import fr.cephb.joperon.core.entities.SnpSeq;
import fr.cephb.joperon.core.entities.XPanel;
import fr.cephb.joperon.tool.OperonFactory;

public class SNPInfo
	{
	private Pattern RS_PATTERN= Pattern.compile("rs[0-9]+",Pattern.CASE_INSENSITIVE);
	private Pattern delimiter=Pattern.compile("[\t]");
	private OperonFactory factory=null;
	
	private int colIndex=0;
	private Set<Assembly> assemblies=new HashSet<Assembly>();
	private InputSource inputSource=null;
	private Set<MPanel> mPanels=new HashSet<MPanel>();
	
	private Set<String> prjdnaId=new HashSet<String>();
	private boolean taqman=false;
	private boolean fasta=false;
	private boolean illumina=false;
	private boolean snplex=false;
	private boolean sequenom=false;
	
	
	public SNPInfo()
		{
		}
	
	public void setFactory(OperonFactory factory) {
		this.factory = factory;
		}
	
	public OperonFactory getFactory() {
		return factory;
		}
	
	public void setAssemblies(Set<Assembly> assemblies) {
		this.assemblies = assemblies;
		}
	
	public Set<Assembly> getAssemblies() {
		return assemblies;
		}
	
	public void setInputSource(InputSource inputSource) {
		this.inputSource = inputSource;
		}
	
	public InputSource getInputSource() {
		return inputSource;
		}
	
	public void setColIndex(int colIndex) {
		this.colIndex = colIndex;
		}
	
	public int getColIndex() {
		return colIndex;
		}
	
	public void setDelimiter(Pattern delimiter) {
		this.delimiter = delimiter;
		}
	
	public Pattern getDelimiter() {
		return delimiter;
		}
	
	public void setMPanels(Set<MPanel> panels) {
		mPanels = panels;
		}
	
	public Set<MPanel> getMPanels() {
		return mPanels;
		}
	
	/** set the individuals we want the genotype */
	public void setPrjdnaIds(Set<String> prjdnaids) {
		this.prjdnaId.clear();
		for(String s: prjdnaids)
			{
			this.prjdnaId.add(s);
			}
		}
	
	/** anwser the individuals we want the genotype */
	public Set<String> getPrjdnaIds() {
		return prjdnaId;
		}
	
	/**  we want the ouput of the markers as FASTA */
	public void setFasta(boolean fasta) {
		this.fasta = fasta;
	}
	
	/**  we want the ouput of the markers as Illumina */
	public void setIllumina(boolean illumina) {
		this.illumina = illumina;
	}
	
	/**  we want the ouput of the markers as sequenom */
	public void setSequenom(boolean sequenom) {
		this.sequenom = sequenom;
	}
	
	/**  we want the ouput of the markers as taqman */
	public void setTaqman(boolean taqman) {
		this.taqman = taqman;
	}
	
	/**  we want the ouput of the markers as snplex */
	public void setSnplex(boolean snplex) {
		this.snplex = snplex;
	}
	
	
	/**
	 * create the result and put a Zip File in the given directory
	 * @param directory
	 * @return a zip file containing the results
	 * @throws IOException
	 * @throws DatabaseException
	 */
	public File makeZip(File directory) throws IOException, DatabaseException
		{
		if(directory==null) directory=new File(System.getProperty("java.io.tmpdir", "/tmp"));
		if(getFactory()==null) throw new IOException("OperonFacory is null");
		if(getInputSource()==null) throw new IOException("Input source is null");
		if(getAssemblies()==null || getAssemblies().isEmpty()) throw new IOException("no assembly");
		if(getColIndex()<0) throw new IOException("bad column index");
		
		Map<PrjDna,String[]> prjdna2individuals=new HashMap<PrjDna,String[]>();
		Map<String,GtypesDB> population2gtypeDB= new HashMap<String, GtypesDB>();
		
		
		List<MPanel> allMPanels= getFactory().getMPanel().getValues();
		boolean selectedMPanelIndexes[]=new boolean[allMPanels.size()];
		Arrays.fill(selectedMPanelIndexes, false);
		for(int i=0;i< allMPanels.size();++i)
			{
			selectedMPanelIndexes[i]=getMPanels().contains(allMPanels.get(i));
			}
		
		String prefix= "OP"+Ymd.now()+"/";
		TmpWriter log_errors= new TmpWriter(directory);
		Set<TmpWriter> tmpFiles= new HashSet<TmpWriter>();
		tmpFiles.add(log_errors);
		
		
		for(String prjdnaId: getPrjdnaIds())
			{
			PrjDna prjdna = getFactory().getPrjDna().get(prjdnaId);
			if(prjdna==null)
				{
				log_errors.println("Cannot get prjdna "+prjdnaId);
				continue;
				}
			prjdna2individuals.put(prjdna, prjdna.getItems());
			}
		
		
		File zipFile= File.createTempFile("operon_", ".snps.zip",directory);
		ZipOutputStream zout= new ZipOutputStream(new FileOutputStream(zipFile));
		
		//loop over assemblies
		for(Assembly build: getAssemblies())
			{
			
			
			
			
			TmpWriter out=null;
			TmpWriter notfound=null;
			TmpWriter mulitple_pos=null;
			TmpWriter taqman_out=null;
			TmpWriter fasta_out=null;
			TmpWriter snplex_out=null;
			TmpWriter illumina_out=null;
			TmpWriter sequenom_out=null;
			BufferedReader r=null;
			
			if(!getFactory().isSnpIdsExists(build))
				{
				log_errors.println("Index of SNP not available for "+build);
				continue;
				}
			if(!getFactory().isSnpMapExists(build))
				{
				log_errors.println("Map of SNP not available for "+build);
				continue;
				}
			
			if(!getFactory().isXPanelExists(build))
				{
				log_errors.println("Warning Genotypes XPANEL not available for "+build);
				//continue; NON
				}
			
			if(
				(this.taqman || this.snplex || this.illumina || this.sequenom) &&
				!getFactory().isSnpSeqExists(build))
				{
				log_errors.println("Assay Sequences not available for "+build);
				continue;
				}
			
			if(this.fasta && !getFactory().isSnpSeqExists(build))
				{
				log_errors.println("Fasta Sequences not available for "+build);
				continue;
				}
			
			
			
			try {
				assert(population2gtypeDB.isEmpty());
				for(String prjdnaId: getPrjdnaIds())
					{
					GtypesDB gdb= new GtypesDB(factory.getOperon(),build,prjdnaId);
					if(! gdb.exists() )
						{
						log_errors.println("not gtypes for "+gdb.getName()+" "+prjdnaId);
						continue;
						}
					gdb.open();
					population2gtypeDB.put(prjdnaId, gdb);
					}
				
				
				
				r = new BufferedReader(getInputSource().getReader());
				out = new TmpWriter(directory);
				String line=null;
				int nLine=0;
				while((line=r.readLine())!=null)
					{
					++nLine;
					String token[]= getDelimiter().split(line);
					if(nLine==1)
						{
						//print header
						for(int i=0;i<token.length;++i)
							{
							if(i>0) out.print("\t");
							out.print(token[i]);
							if(i!=this.colIndex) continue;
							out.print("\t");
							out.print("snp-id-count");
							out.print("\t");
							out.print("fid");
							out.print("\t");
							out.print("Chrom");
							out.print("\t");
							//out.print("Position");
							//out.print("\t");
							out.print("P");
							out.print("\t");
							out.print("Q");
							out.print("\t");
							out.print("Ctg");
							out.print("\t");
							out.print("Ori");
							out.print("\t");
							out.print("X");
							out.print("\t");
							out.print("Y");
							out.print("\t");
							out.print("Map-Weight");
							out.print("\t");
							out.print("Cat");
							out.print("\t");
							out.print("Ca");
							out.print("\t");
							out.print("Ra");
							out.print("\t");
							out.print("Aa");
							out.print("\t");
							out.print("Flags");
							
							
							if(getFactory().isXPanelExists(build))
								{
								for(int j=0;j< selectedMPanelIndexes.length;++j)
									{
									if(!selectedMPanelIndexes[j]) continue;
									MPanel mpane= allMPanels.get(j);
									out.print("\t");
									out.print(mpane.getSyn()+".AA");
									out.print("\t");
									out.print(mpane.getSyn()+".AB");
									out.print("\t");
									out.print(mpane.getSyn()+".BB");
									out.print("\t");
									out.print(mpane.getSyn()+".Freq");
									}
								}
							
							for(PrjDna prjdna: prjdna2individuals.keySet())
								{
								for(PrjDna.Individual indi:prjdna.getIndividuals())
									{
									out.print("\t");
									out.print(prjdna.getAcn()+"."+indi.getName());
									}
								}
							
							}
						out.println();
						continue;
						}
					if(line.trim().isEmpty()) continue;
					if(token.length<= getColIndex())
						{
						log_errors.println("Line "+nLine+" not enough columns");
						continue;
						}
					
					String rsName= token[getColIndex()].trim();
					if(RS_PATTERN.matcher(rsName).matches()) rsName=rsName.toLowerCase();
					
					List<SnpIds> snpids= getFactory().getSnpIds(build).get(rsName);
					if(snpids.isEmpty())
						{
						if(notfound==null) notfound= new TmpWriter(directory);
						notfound.println(line);
						continue;
						}
					
					if(snpids.size()!=1)
						{
						if(mulitple_pos==null) mulitple_pos= new TmpWriter(directory);
						mulitple_pos.println(rsName+"\t"+snpids.size());
						}
					
					for(SnpIds snpid:snpids)
						{
						
						for(int i=0;i<token.length;++i)
							{
							if(i>0) out.print("\t");
							out.print(token[i]);
							if(i!=this.colIndex) continue;
								
							Fid fid=snpid.getFid();
							out.print("\t");
							out.print(snpids.size());
							out.print("\t");
							out.print(fid.asString());
							out.print("\t");
							SnpMap map= getFactory().getSnpMap(build).get(fid);
							if(map==null)
								{
								log_errors.println("No SnpMap for "+fid+" "+build);
								out.println("#ERROR");
								continue;
								}
							out.print(map.getChr());
							out.print("\t");
							//out.print(map.getPosition());
							//out.print("\t");
							out.print(map.getP());
							out.print("\t");
							out.print(map.getQ());
							out.print("\t");
							out.print(map.getCtg());
							out.print("\t");
							out.print(map.getOrientation());
							out.print("\t");
							out.print(map.getX());
							out.print("\t");
							out.print(map.getY());
							out.print("\t");
							out.print(map.getMw());
							out.print("\t");
							out.print(map.getCategory());
							out.print("\t");
							out.print(map.getCa());
							out.print("\t");
							out.print(map.getRa());
							out.print("\t");
							out.print(map.getAa());
							out.print("\t");
							out.print(map.getFlags());
							
							
							/* FASTA */
							if(this.fasta)
								{
								if(fasta_out==null) fasta_out= new TmpWriter(directory);
								SnpSeq snpseq= getFactory().getSnpSeq(build).get(map.getFid());
								if(snpseq==null)
									{
									log_errors.println("cannot get snpseq for "+token[0]+" fid "+fid+"/"+build);
									}
								else
									{
									fasta_out.println(">"+rsName+
												"|taxid:"+build.getTaxId()+
												"|source:"+build.getSource()+
												"|version:"+build.getVersion()+
												"|"+snpseq.getTitle());
									fasta_out.println(snpseq.getOne()+
											AcidNucleicUtil.dnastr_alleles_to_iupac(snpseq.getRef()+"/"+snpseq.getAlt())+
											snpseq.getTwo());
									}
								}
							
							/* ASSAY */
							if(this.taqman || this.snplex || this.illumina || this.sequenom)
								{
								
								PrjSeq snpseq= getFactory().getPrjSeq(build).get(map.getFid());
								if(snpseq==null)
									{
									log_errors.println("cannot get PrjSeq for fid "+fid+"/"+build+" This SNP may be unsuitable for a design");
									}
								/* TAQMAN */
								if(snpseq!=null && this.taqman)
									{
									if(taqman_out==null) taqman_out= new TmpWriter(directory);
									taqman_out.println(snpseq.asTaqmanRow());
									}
								if(snpseq!=null && this.snplex)
									{
									if(snplex_out==null) snplex_out= new TmpWriter(directory);
									snplex_out.println(snpseq.asSnplexRow());
									}
								
								if(snpseq!=null && this.illumina)
									{	
									if(illumina_out==null)
										{
										illumina_out=new TmpWriter(directory);
										illumina_out.println("SNP_Name,Sequence,Genome_Build,Chr,Coordinate,Source,dbSNP_Version,Ploidy,Species,Customer_Strand");
										}
									illumina_out.println(snpseq.asIlluminaRow(build));
									}
								if(snpseq!=null && this.sequenom)
									{	
									if(sequenom_out==null)
										{
										sequenom_out=new TmpWriter(directory);
										sequenom_out.println("SNP_ID\tSequence");
										}
									sequenom_out.println(snpseq.asSequenomRow());
									}
								}
							
							List<XPanel> xpanels = null;
							if(getFactory().isXPanelExists(build))
								{
								xpanels = factory.getXPanel(build).getConcatenedValues(fid);
								}
							for(int j=0;xpanels!=null && j <xpanels.size() && j< selectedMPanelIndexes.length;++j)
								{
								if(!selectedMPanelIndexes[j]) continue;
								XPanel xpanel= xpanels.get(j);
								out.print("\t");
								out.print(xpanel.getAa());
								out.print("\t");
								out.print(xpanel.getAb());
								out.print("\t");
								out.print(xpanel.getBb());
								out.print("\t");
								out.print(xpanel.getFreq());
								}
								
							for(PrjDna prjdna: prjdna2individuals.keySet())
								{
								GtypesDB gdb= population2gtypeDB.get(prjdna.getAcn());
								List<Gtypes> genotypes = null;
								if(gdb!=null)
									{
									genotypes = gdb.getConcatenedValues(fid);
									}
								String items[]= prjdna2individuals.get(prjdna);
								for(int j=0;j< items.length;++j)
									{
									out.print("\t");
									if(genotypes==null || genotypes.size()<=j)
										{
										//log_errors.print("for "+fid+":"+genotypes);
										out.print("00");
										}
									else
										{
										out.print(genotypes.get(j).getAlleles());
										}
									}
								}
							
							}
						out.println();
						}
					}
					
				if(nLine==0)
					{
					log_errors.println("Input is empty");
					}
				else if(nLine==0)
					{
					log_errors.println("No data found in input");
					}
				
				if(notfound!=null && !notfound.isEmpty())
					{
					notfound.copyToZip(zout,prefix+"NOT_FOUND_"+build.getShortCode()+".txt");
					}
				if(mulitple_pos!=null && !mulitple_pos.isEmpty())
					{
					mulitple_pos.copyToZip(zout,prefix+"MULTIPLE_"+build.getShortCode()+".txt");
					}
				if(taqman_out!=null && !taqman_out.isEmpty())
					{
					taqman_out.copyToZip(zout,prefix+"TAQMAN_"+build.getShortCode()+".txt");
					}
				if(fasta_out!=null && !fasta_out.isEmpty())
					{
					fasta_out.copyToZip(zout,prefix+"FASTA_"+build.getShortCode()+".txt");
					}
				if(snplex_out!=null && !snplex_out.isEmpty())
					{
					snplex_out.copyToZip(zout,prefix+"SNPLEX_"+build.getShortCode()+".txt");
					}
				if(illumina_out!=null && !illumina_out.isEmpty())
					{
					illumina_out.copyToZip(zout,prefix+"ILMN_"+build.getShortCode()+".txt");
					}
				if(sequenom_out!=null && !sequenom_out.isEmpty())
					{
					sequenom_out.copyToZip(zout,prefix+"SEQNOM_"+build.getShortCode()+".txt");
					}
				out.copyToZip(zout,prefix+build.getShortCode()+".txt");
				} 
			catch (Exception e)
				{
				PrintWriter pw= new PrintWriter(log_errors);
				e.printStackTrace(pw);
				pw.flush();
				}
			finally
				{
				
				for(String pop: population2gtypeDB.keySet())
					{
					GtypesDB gdb=population2gtypeDB.get(pop);
					if(gdb==null) continue;
					gdb.safeClose();
					}
				population2gtypeDB.clear();
				
				
				if(r!=null) r.close();
				if(out!=null) out.delete();
				if(notfound!=null) notfound.delete();
				if(mulitple_pos!=null) mulitple_pos.delete();
				if(taqman_out!=null) taqman_out.delete();
				if(illumina_out!=null) illumina_out.delete();
				if(sequenom_out!=null) sequenom_out.delete();
				if(snplex_out!=null) snplex_out.delete();
				if(fasta_out!=null) fasta_out.delete();
				}
			}
		
		
		if(!log_errors.isEmpty())
			{
			log_errors.copyToZip(zout, prefix+"ERRORS.txt");
			}
		zout.flush();
		zout.close();
		
		//cleanup
		for(TmpWriter f:tmpFiles) f.delete();
		return zipFile;
		}
	

	public static void main(String[] args)
		{
		Operon operon=null;
		TreeSet<String> mpanelsSelected= new TreeSet<String>(String.CASE_INSENSITIVE_ORDER);
		SNPInfo app=new SNPInfo();
		try {
			int optind=0;
			
			while(optind<args.length)
				{
				if(args[optind].equals("-h"))
					{
					System.err.println("SnpInfo");
					System.err.println(" -c column index [default:1]");
					System.err.println(" -d delimiter [regex]");
					System.err.println(" -mpanel <String> add mpanel (default all)");
					}
				else if(args[optind].equals("-d"))
					{
					app.delimiter= Pattern.compile(args[++optind]);
					}
				else if(args[optind].equals("-c"))
					{
					app.colIndex= Integer.parseInt(args[++optind])-1;
					if(app.colIndex<0)
						{
						System.err.println("Illegal column index");
						return;
						}
					}
				else if(args[optind].equals("-mpanel"))
					{
					for(String s:args[++optind].split("[,]"))
						{
						if(s.length()==0) continue;
						mpanelsSelected.add(s);
						}
					}
				else if(args[optind].equals("--"))
					{
					++optind;
					break;
					}
				else if(!args[optind].startsWith("-"))
					{
					break;
					}
				else 
					{
					System.err.println("unknown option "+args[optind]);
					return;
					}
				
				++optind;
				}
			
			
			
			operon= Operon.newInstance();
			app.setFactory(new OperonFactory(operon));
			app.setColIndex(0);
			app.setInputSource(new InputSource.FILE("/operon/operon_home/small_rs_list.txt.gz"));
			MPanel mpanel=app.getFactory().getMPanel().findMPanelBySyn("CEU");
			Set<MPanel> mpanels= new HashSet<MPanel>();
			if(mpanel!=null) mpanels.add(mpanel);
			app.setMPanels(mpanels);
			
			File zip =app.makeZip(null);
			System.err.println(zip);
			
			ZipFile zf= new ZipFile(zip);
			Enumeration<? extends ZipEntry> ze=zf.entries();
			while(ze.hasMoreElements())
				{
				System.err.println(ze.nextElement());
				}
			
			if(optind==args.length)
				{
				//app.run(new BufferedReader(new InputStreamReader(System.in)));
				}
			else if(optind+1==args.length)
				{
				BufferedReader r= new BufferedReader(new FileReader(args[optind]));
				//app.run(r);
				r.close();
				}
			else
				{
				System.err.println("Illegal number of arguments");
				}
			} 
		catch (Exception e)
			{
			e.printStackTrace();
			}
		finally
			{
			if(app.factory!=null) app.factory.close();
			if(operon!=null) operon.safeClose();
			}
		}
	}
